iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
AI Engineering

從呼叫 API 到打造 Gateway:LLM 工程化 30 天系列 第 19

Day 19|一個案例,拆解到底:怎麼判斷是不是真的在做 Harness

  • 分享至 

  • xImage
  •  

Day 19|一個案例,拆解到底:怎麼判斷是不是真的在做 Harness

前兩天分享了「為什麼需要 harness」跟「harness 是甚麼」,道理都懂,但實際上要如何判斷:這個修法,到底算不算 harness?。底下透過一個具體情境,把整個判斷過程走一遍,應該能更理解harness的運作過程。

情境:一個會退款的客服 Agent

假設今天在做一個客服agent,其中一項能力是幫使用者辦理退款。某天你發現了一個大問題,當使用者網路不穩或重複發送請求後,同一個退款請求送了兩次,為沒有記住「這筆訂單已經退過款」,兩次都執行了退款。

這是一個代價很明確的錯誤,不是「回答得不夠精確」那種模糊的品質問題,是多退了一筆錢

Harness 的目標不是「讓 Agent 不犯錯」

在往下講之前,有一個概念想先釐清。

最直覺的想法,agent 會犯這個錯,是因為它「沒想到」要檢查訂單有沒有退過款,所以只要想辦法讓它「想到」,問題就解決了。這個想法背後其實藏著一個假設,只要判斷力夠好、提醒夠到位,agent 最終就不會犯這個錯

但這個假設本身就有問題。Agent 的判斷會不會失誤,取決於太多當下的變因:對話累積了多少 context、使用者這次的問法急不急、模型那次抽樣的結果剛好是什麼,這些都不是能完美控制的。判斷失誤是一種常態,不是一次修好就能根絕的東西。

真正該問的問題,因此不是「怎麼讓 agent 這次不犯這個錯」,而是:如果 agent 這次真的又犯了這個錯,重複退款這個後果,會不會真的發生在使用者的帳戶裡? 也就是說,Harness 要顧的不是 agent 的判斷力,而是系統的邊界,判斷失誤可以發生,但後果不應該穿透這道邊界,變成一個真實、不可接受的結果。

第一個直覺:改 prompt

最快的反應通常是回頭改 system prompt,加一句:「執行退款前,請先確認這筆訂單是否已經退過款,避免重複退款。」

這樣做完,重新測試幾次,agent 真的變聰明了,它會先問「這筆訂單是不是已經退過了?」,問題看似解決。

但回到我們真正該問的問題:如果 agent 這一次還是漏掉了這句提醒——對話變長、context 塞了更多其他資訊分散注意力,或使用者換一種更急促的說法問——重複退款這個後果,有沒有任何東西能攔住它?

答案是沒有。這句提醒完全依賴 agent 那一次的推理有沒有把它納入判斷,一旦沒有,process_refund 這個動作就會直接執行下去,錢就是真的被退了兩次。系統本身沒有任何邊界能擋住這件事,唯一的防線就是 agent 那一次「有沒有想到」——這代表這個修法根本沒有碰到系統邊界,它試圖做的,始終是「讓 agent 這次不犯錯」,而不是「就算犯錯也不會出事」。

往前一步:寫進指令檔案

如果 agent 可以有一份持續維護的指令檔案(像 AGENTS.md),把這條規則正式寫進去,而不是每次臨時塞進 system prompt,情況會有實質的進步。這條規則現在會被每一次任務都帶到,不會因為某次忘了寫進 prompt 而消失,而且它會跟其他觀察到的錯誤規則累積在一起,變成一份會持續變厚、持續反映真實失敗經驗的文件。

即使用了指令檔案,讓 agent有一份規則遵循,還是沒辦法保證模型在哪個瞬間突然出包,不照著指令執行。在這種情況下,系統還是沒辦法準確的攔住agent的錯誤動作。

指令檔案終究是一份「寫給模型看」的文件,它能不能發揮作用,仍然完全取決於模型「讀懂、並且選擇遵守」。就算它把犯錯的機率壓得更低,一旦真的失手,換了一個推理能力較弱的模型、或者這份檔案已經累積得又長又雜、重要規則被稀釋在中間,重複退款這個動作,一樣會毫無阻攔地被執行。指令檔案讓 agent「更不容易」犯錯,但完全沒有替系統設下任何邊界,防線依然完全押在 agent 的判斷力上。

真正的解法:在系統邊界上放一道閘門

與其想辦法讓 agent「別犯這個錯」,不如乾脆假設它一定會有犯這個錯的時候,然後把查證「這筆訂單是否已經退過款」變成一個流程,讓程式不用透過agent的判斷,就可以精準的擋下重複退款。

於是解法會變成類似這樣:

def process_refund(order_id, amount):
    if refund_already_issued(order_id):
        return f"訂單 {order_id} 已經退過款,拒絕重複執行"
    execute_refund(order_id, amount)
    mark_refund_issued(order_id)
    return f"訂單 {order_id} 退款完成"

注意這段程式碼完全沒有試圖讓 agent「變聰明」。agent 依然可以毫無察覺地呼叫了 process_refund 兩次,它的推理照樣沒有把「這筆訂單退過款」納入考量,判斷失誤照樣發生了。差別在於,這個失誤這次沒有機會變成真實後果,因為 refund_already_issued 這一道檢查,擋在「agent 做了什麼決定」跟「這個決定真的造成了什麼影響」之間,把兩者切開了。

再回頭看那三個判斷問題,這次答案會不一樣:

  • **會不會因為模型這次推理對就失效?**不會,因為這道閘門根本不在乎模型有沒有推理對——就算模型完全沒想到要檢查,閘門一樣會擋下第二次退款
  • **同樣的錯下次還會不會再犯?**agent 的判斷失誤依然可能反覆發生,但「因為這個判斷失誤而真的多退一筆錢」這個後果,不會再發生——犯錯的行為跟犯錯的後果,在這裡被拆成了兩件事
  • **換一個模型還有效嗎?**有效,因為這道閘門完全架設在系統這一側,跟模型的判斷能力毫無關係

三個階段的真正差異:不是「越來越聰明」,而是「邊界越來越明確」

回頭看這三個階段,會發現它們的差異,不是原本以為的「防線越推越前面、越推越不容易犯錯」,而是系統邊界的存在感從無到有:

  • 改 prompt:完全沒有系統邊界,agent 的判斷就是唯一防線,一旦失守,後果直接發生
  • 指令檔案:依然沒有系統邊界,只是把「唯一防線」的失守機率降低了一些
  • 程式化檢查:第一次真正立起一道系統邊界——這道邊界不管 agent 判斷對不對,只管「這個決定會不會被允許造成後果」

這也是為什麼「重複退款」是一個很漂亮的 harness 案例——它清楚示範了 harness 真正在乎的問題,從來不是「agent 這次會不會想到」,而是**「就算它沒想到,這件事會不會真的變成一個使用者要承擔的損失」**。

這不是「有或沒有」,是「這個邊界,值得蓋在哪裡」

這也連回 Day 18 提過的、指令檔案跟程式化工具並不是二選一的關係。一份持續累積的指令檔案,依然有它的價值,它能降低 agent 判斷失誤的機率,對於後果不嚴重、就算真的犯錯也沒有太大代價的情境(例如用詞不夠精準、格式稍微不整齊),把力氣花在降低機率就夠了,不需要為每一件小事都蓋一道系統邊界。

但像「重複退款」這種後果明確、代價是真金白銀、而且錯誤模式本身可以被程式判斷的情境,只停在「讓 agent 更不容易犯錯」這個層次,其實是低估了問題,真正該做的工程判斷,是分辨哪些錯誤的後果,重要到值得在系統邊界上專門為它蓋一道閘門,而不是每一種可能性都指望 agent 的判斷力扛下來。

小結

判斷一個修法是不是「真的在做 harness」,關鍵不在於它讓 agent 犯錯的機率降了多少,而在於:如果 agent 這次還是犯了這個錯,這個錯誤的後果,有沒有一道邊界攔在它跟真實世界之間? 改 prompt 跟寫指令檔案,做的都是降低犯錯機率,但沒有替系統設下任何邊界,一旦失守,後果直接發生;程式化檢查則是承認「agent 一定會有犯錯的時候」,把力氣花在確保犯錯的後果出不去。Harness 的價值,從來不是讓 agent 不犯錯,而是讓 agent 犯錯的時候,錯誤不容易穿透系統邊界,變成一個不可接受的結果。

目前把harness的核心概念、定義都分享完了,明天會進入harness的最後一天文章!


上一篇
Day 18|Harness 的組成元件:把「環境」拆開來看
下一篇
Day 20|動手蓋一個最小可行的 Harness
系列文
從呼叫 API 到打造 Gateway:LLM 工程化 30 天22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言